/**
 * 客户端socket封装
 * 基于SocketBase实现了具体的WebSocket客户端功能，包括：
 * - 消息序列号管理
 * - 心跳机制
 * - 权限验证
 * - 消息回调处理
 * @date 2024.9.10
 * @author wz
 */

import { MsgType, Protocol, ProtocolMap } from "../../../proto/msg";
import { SocketMsgCallback, SocketReason, SocketRespWrap, SocketReqWrap, SocketMsgData, SocketConst } from "./BaseTypes";
import { SocketBase } from "./SocketBase";


/**
 * 客户端socket(可以直接使用也可以继承修改)
 */
export class ClientSocket extends SocketBase {
    /**
     * 消息序列号，用于标识每个发送的消息
     * 每次发送消息时递增，便于对应请求和响应
     */
    private _seq: number = 0;

    /**
     * 发送数据的回调映射表
     * 以消息序列号为键，存储对应消息的回调函数
     */
    private _callMap: Map<number, SocketMsgCallback> = new Map();


    /**
     * 心跳定时器句柄
     * 用于定期发送心跳包
     */
    private _heartBeatHandle: number = 0;

    /**
     * 心跳超时定时器句柄
     * 用于检测心跳响应超时
     */
    private _heartTimeOutHanlde: number = 0;

    /**
     * 连接成功回调函数
     * 当连接成功或权限验证成功后调用
     */
    private _connectCallback: Function = null;

    /**
     * 连接关闭回调函数
     * 当连接关闭时触发，外部可设置
     */
    public onCloseCallback: (reason: SocketReason) => void = null;

    /**
     * 消息接收回调函数
     * 当收到服务器消息时触发，外部可设置
     */
    public onMsgCallback: (msgType: MsgType, resp: SocketRespWrap) => void = null;

    /**
     * 转换待发送的数据格式
     * 将请求数据包装成适合传输的格式
     * @param reqWrap 请求包装数据
     * @param callback 发送回调函数
     * @returns 转换后的消息数据
     */
    override convertSendData(reqWrap: SocketReqWrap, callback?: SocketMsgCallback): SocketMsgData {
        const cmd = reqWrap.cmd;
        // 检查消息号是否存在于协议映射表中
        if (!ProtocolMap.has(cmd)) {
            console.warn(`消息号不存在:${cmd}`);
            return;
        }

        // 获取对应的请求结构体
        const req = proto[ProtocolMap.get(cmd).req]
        if (!req) {
            console.warn(`消息号：${cmd}==>对应发送结构体不存在`);
            return;
        }

        // 为非心跳消息分配序列号并存储回调
        let seq = 0
        if (cmd != Protocol.BASE_HEART) {
            this._seq = ++this._seq % 1000000000
            if (this._seq == 0) this._seq = 1
            seq = this._seq
            this._callMap.set(seq, callback)
        }

        // 编码消息体
        const data: Uint8Array = req.encode(reqWrap.body).finish()
        const sendReqWrap = proto.ReqWrap.encode({ cmd: reqWrap.cmd, seq, body: data }).finish()

        console.log(`发送消息：${cmd}==>${JSON.stringify(reqWrap.body)}`)
        return { msgType: MsgType.SNED, data: sendReqWrap };
    }

    /**
     * 转换接收到的数据格式
     * 将二进制数据解析为业务可用的结构
     * @param msgData 收到的消息数据
     * @returns 转换后的响应包装数据
     */
    override  convertRecvData(msgData: SocketMsgData): SocketRespWrap {
        const msgType = msgData.msgType;

        if (msgType == MsgType.SNED) {
            // 解码通用响应包装
            const data = proto.RespWrap.decode(msgData.data)
            const cmd = data.cmd
            if (cmd == Protocol.BASE_AUTH) {
                this.onAuthSuccess()
                return { cmd: Protocol.BASE_AUTH, body: {}, msg: "权限验证成功", code: proto.CODE.SUCCESS };
            } else if (cmd == Protocol.BASE_HEART) {
                if (this._heartTimeOutHanlde)
                    clearTimeout(this._heartTimeOutHanlde);
                return { cmd: Protocol.BASE_HEART, body: {}, msg: "心跳返回", code: proto.CODE.SUCCESS };
            }

            // 获取对应的响应结构体并解码
            const resp = proto[ProtocolMap.get(cmd).resp]
            if (!resp) {
                console.warn(`消息号：${cmd}==>对应接收结构体不存在`);
            }
            const body = resp.decode(data.body)

            // 构建响应数据包装
            const respWrap: SocketRespWrap = { cmd, body, msg: data.msg, code: data.code };
            console.log(`收到消息：${cmd}==>${JSON.stringify(body)}`)
            // 如果存在对应的回调，则调用并删除
            if (this._callMap.has(data.seq)) {
                this._callMap.get(data.seq)(respWrap)
                this._callMap.delete(data.seq)
            }
            return respWrap
        }

        if (msgType == MsgType.PUSH) {
            console.log('推送:', msgData.data)
            // 解码通用响应包装
            const data = proto.PushWrap.decode(msgData.data)
            const cmd = data.cmd
            // 获取对应的响应结构体并解码
            const resp = proto[ProtocolMap.get(cmd).resp]
            if (!resp) {
                console.warn(`消息号：${cmd}==>对应接收结构体不存在`);
            }
            const body = resp.decode(data.body)

            // 构建响应数据包装
            const respWrap: SocketRespWrap = { cmd, body, msg: '推送消息', code: proto.CODE.SUCCESS };
            console.log(`收到推送消息：${cmd}==>${JSON.stringify(body)}`)
            return respWrap
        }
    }

    /**
     * 连接成功回调
     * WebSocket连接建立成功时调用
     */
    override onConnectSuccess() {
        // 如果不需要权限验证，则直接触发回调
        if (SocketConst.isOpenAuth) {
            this._sendAuth();
        } else {
            this._connectCallback && this._connectCallback();
            this._connectCallback = null;
            // 如果启用心跳，则开始心跳
            if (SocketConst.isOpenHeart) {
                this._startHeart();
            }
        }
        console.log("连接成功");
    }

    /**
     * 权限验证成功回调
     * 当权限验证通过时调用
     */
    override onAuthSuccess() {
        console.log("权限验证成功");
        // 如果启用权限验证，则在验证成功后触发回调
        if (SocketConst.isOpenAuth) {
            this._connectCallback && this._connectCallback();
            this._connectCallback = null;
            // 如果启用心跳，则开始心跳
            if (SocketConst.isOpenHeart) {
                this._startHeart();
            }
        }
    }

    /**
     * 连接关闭回调
     * 当连接关闭时调用
     * @param reason 关闭原因
     */
    override onConnectClose(reason: SocketReason) {
        // 计数清零
        this._seq = 0;
        // 清理回调
        this._callMap.clear();
        // 清理心跳
        this._clearHeart()

        this._connectCallback = null;
        // 根据不同关闭原因打印日志
        switch (reason) {
            case SocketReason.FORCE:
                break;
            case SocketReason.CLOSE:
                console.log("连接断开");
                break;
            case SocketReason.TIMEOUT:
                console.log("连接超时");
                break;
            case SocketReason.HEART:
                console.log("心跳超时");
                break;
        }
        // 触发外部设置的关闭回调
        this.onCloseCallback && this.onCloseCallback(reason);
    }

    /**
     * 消息响应处理
     * 当收到服务器消息时调用
     * @param msgType 消息类型标识符
     * @param respWrap 响应包装数据
     */
    override onMessege(msgType: MsgType, respWrap: SocketRespWrap) {
        try {
            // 触发外部设置的消息回调
            this.onMsgCallback && this.onMsgCallback(msgType, respWrap);
        }
        catch (e) {
            console.error(`Error processing message ID ${respWrap.cmd}:`, e.message);
        }
    }


    /**
     * 连接服务器
     * 重写父类方法，添加连接回调
     * @param ip 服务器地址
     * @param port 服务器端口
     * @param callback 连接成功回调
     * @param outTime 连接超时时间（秒）
     */
    override connect(ip: string, port: string, callback?, outTime: number = 5) {
        super.connect(ip, port, outTime);
        this._connectCallback = callback;
    }

    /**
     * 发送权限验证消息
     * 使用预设的授权码进行验证
     */
    private _sendAuth() {
        this.sendData(Protocol.BASE_AUTH, { authcode: SocketConst.authcode })
    }

    /**
     * 开启心跳机制
     * 定期发送心跳包并检测响应超时
     */
    protected _startHeart() {
        this._clearHeart();
        this._sendHeart();
        // 设置定时器，定期发送心跳
        this._heartBeatHandle = setInterval(() => {
            this._sendHeart();
        }, SocketConst.heartSendInterval * 1000);
    }

    /**
     * 清理心跳相关定时器
     */
    private _clearHeart() {
        if (this._heartBeatHandle)
            clearInterval(this._heartBeatHandle);
        if (this._heartTimeOutHanlde)
            clearTimeout(this._heartTimeOutHanlde)
        this._heartBeatHandle = 0;
        this._heartTimeOutHanlde = 0;
    }

    /**
     * 发送心跳包并启动超时检测
     */
    private _sendHeart() {
        // 设置心跳响应超时检测
        this._heartTimeOutHanlde = setTimeout(() => {
            console.log("heart timeout");
            this._onclose(SocketReason.HEART);
        }, SocketConst.heartTimeout * 1000);
        // 发送心跳包
        this.sendData(Protocol.BASE_HEART);
    }
}